package com.plugin.appinit

import com.android.build.api.transform.Format
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformException
import com.android.build.api.transform.TransformInvocation
import com.appinit.lib.annotation.Initial
import com.appinit.lib.annotation.AppInitial
import com.google.common.collect.Sets
import com.google.gson.Gson
import groovy.io.FileType
import javassist.ClassPath
import javassist.ClassPool
import javassist.CtClass
import org.apache.commons.io.FileUtils
import org.gradle.api.Project
import org.gradle.api.logging.Logger

import java.lang.reflect.Constructor;

/**
 * @author GYL
 * @dateTime 2020/5/18-15:58
 * @description
 */
class InitiatorTransform extends Transform{

  Project mProject
  CtClass appCtClass
  File appDest

  CtClass appProviderClass
  File appProviderDest

  //CtClass appInitManagerClass
  //File appInitManagerDest

  def initPath = new ArrayList<AppInitItem>()
  static Logger logger

  InitiatorTransform(Project project) {
    mProject = project
    logger=mProject.logger

  }

  @Override
  String getName() {
    return "initiator"
  }

  @Override
  Set<QualifiedContent.ContentType> getInputTypes() {
    return Collections.singleton(QualifiedContent.DefaultContentType.CLASSES)
  }

  @Override
  Set<QualifiedContent.Scope> getScopes() {
    if (mProject.plugins.hasPlugin("com.android.application")) {
      return Sets.immutableEnumSet(
        QualifiedContent.Scope.PROJECT,
        QualifiedContent.Scope.SUB_PROJECTS,
        QualifiedContent.Scope.EXTERNAL_LIBRARIES)
    } else if (mProject.plugins.hasPlugin("com.android.library") || mProject.plugins.hasPlugin("java-library")) {
      return Sets.immutableEnumSet(
        QualifiedContent.Scope.PROJECT)
    } else {
      return Collections.emptySet()
    }
  }

  @Override
  boolean isIncremental() {
    return false
  }

  @Override
  void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
    ClassPool classPool = ClassPool.getDefault()
    def classPath = []
    classPool.appendClassPath(mProject.android.bootClasspath[0].toString())
    try {
      Class jarClassPathClazz = Class.forName("javassist.JarClassPath")
      Constructor constructor = jarClassPathClazz.getDeclaredConstructor(String.class)
      constructor.setAccessible(true)

      transformInvocation.inputs.each { input ->
        def subProjectInputs = []
        //each jar
        input.jarInputs.each { jarInput ->
          //mProject.logger.error(">>  jar input=   " + jarInput.file.getAbsolutePath())
          ClassPath clazzPath = (ClassPath) constructor.newInstance(jarInput.file.absolutePath)
          //mProject.logger.error(">> classPath = "+ classPath.toString())
          classPath.add(clazzPath)
          classPool.appendClassPath(clazzPath)

          def jarName = jarInput.name
          if (jarName.endsWith(".jar")) {
            jarName = jarName.substring(0, jarName.length() - 4)
          }

          mProject.logger.error(">>  Dependencies JAR Name: " + jarName)

          if (jarName.startsWith(":")) {
             //mProject.logger.error(">>  jar name startsWith冒号   " + jarName)
            //handle it later, after classpath set
            subProjectInputs.add(jarInput)
          } else {
            def dest = transformInvocation.outputProvider.getContentLocation(jarName,
              jarInput.contentTypes, jarInput.scopes, Format.JAR)
             //mProject.logger.error(">>  jar output path(dest):" + dest.getAbsolutePath())
            FileUtils.copyFile(jarInput.file, dest)
          }
        }

        // Handle library project jar here
        subProjectInputs.each { jarInput ->
          def jarName = jarInput.name
          if (jarName.endsWith(".jar")) {
            jarName = jarName.substring(0, jarName.length() - 4)
          }
          if (jarName.startsWith(":")) {
            // sub project
            File unzipDir = new File(
              jarInput.file.getParent(),
              jarName.replace(":", "") + "_unzip")
            if (unzipDir.exists()) {
              unzipDir.delete()
            }
            unzipDir.mkdirs()
            Decompression.uncompress(jarInput.file, unzipDir)

            File repackageFolder = new File(
              jarInput.file.getParent(),
              jarName.replace(":", "") + "_repackage"
            )

            FileUtils.copyDirectory(unzipDir, repackageFolder)

            unzipDir.eachFileRecurse(FileType.FILES) { File it ->
              checkAndTransformClass(classPool, it, repackageFolder)
            }

            // re-package the folder to jar
            def dest = transformInvocation.outputProvider.getContentLocation(
              jarName, jarInput.contentTypes, jarInput.scopes, Format.JAR)

            Compressor zc = new Compressor(dest.getAbsolutePath())
            zc.compress(repackageFolder.getAbsolutePath())
          }
        }

        input.directoryInputs.each { dirInput ->
          def outDir = transformInvocation.outputProvider.getContentLocation(dirInput.name, dirInput.contentTypes, dirInput.scopes, Format.DIRECTORY)
          classPool.appendClassPath(dirInput.file.absolutePath)
          // dirInput.file is like "build/intermediates/classes/debug"
          int pathBitLen = dirInput.file.toString().length()
          def callback = { File it ->
            def path = "${it.toString().substring(pathBitLen)}"
            if (it.isDirectory()) {
              new File(outDir, path).mkdirs()
            } else {
              boolean handled = checkAndTransformClass(classPool, it, outDir)
              if (!handled) {
                // copy the file to output location
                new File(outDir, path).bytes = it.bytes
              }
            }
          }
          if (dirInput.changedFiles == null || dirInput.changedFiles.isEmpty()) {
            dirInput.file.traverse(callback)
          } else {
            dirInput.changedFiles.keySet().each(callback)
          }
        }
      }

      //FIXME appinitialmanager:目前在AppInitManager中插入代码无法打入apk包中，待修改
//      if(appInitManagerClass!=null){
//        mProject.logger.error(">>  appInitManagerClass==  ${appInitManagerClass.name}    size" + initPath.size())
//        if (initPath.size() > 0) {
//          AppInitialManagerTransform appInitialManagerTransform=new AppInitialManagerTransform(mProject,appInitManagerClass,initPath,classPool)
//          appInitialManagerTransform.handleActivitySaveState()
//          appInitManagerClass.writeFile(appInitManagerDest.absolutePath)
//          appInitManagerClass.detach()
//        }
//      }
      // application
      mProject.logger.error(">>  app init list size" + initPath.size())
      if (initPath.size() > 0) {
        if (appCtClass != null) {
          mProject.logger.error(">>  appCtClass==  ${appCtClass.name}")

          ApplicationTransform applicationTransform = new ApplicationTransform(mProject, appCtClass,initPath, classPool)
          applicationTransform.handleActivitySaveState()
          appCtClass.writeFile(appDest.absolutePath)
          appCtClass.detach()

          //debug
//          def aptOutputDir = mProject.file(new File(mProject.buildDir, "intermediates/merged_assets/debug/out/"))
//          def aptOutput = new File(aptOutputDir, "initList.json")
//          if(!aptOutput.exists()){
//            aptOutput.createNewFile()
//          }
//          String json=new Gson().toJson(initPath)
//          aptOutput.write(json)
//
//          //FileUtils.copyFile(aptOutput, new File(aptOutputDestDir, "initList.json"))
//          // release
//          def aptOutputReleaseDir = mProject.file(new File(mProject.buildDir, "intermediates/merged_assets/release/out/"))
//          def aptOutputRelease = new File(aptOutputReleaseDir, "initList.json")
//          if(!aptOutputRelease.exists()){
//            aptOutputRelease.createNewFile()
//          }
//          aptOutputRelease.write(json)

        }
      }
      // provider



    } finally {
      classPath.each { it ->
        classPool.removeClassPath(it)
      }
    }

  }


  boolean checkAndTransformClass(ClassPool classPool, File file, File dest) {

    mProject.logger.error(">>  classPool:"+classPool.toString())
    mProject.logger.error(">>  file:"+file.getAbsoluteFile())
    mProject.logger.error(">>  dest:"+dest.getAbsoluteFile())

    //APP
    CtClass androidAppCtClass = classPool.get("android.app.Application")
    //ContentProvider 类是实现初始化
    CtClass androidProviderCtClass=classPool.get("android.content.ContentProvider")
    CtClass initCtClass = classPool.get("com.appinit.lib.IAppInitial")
    //
    //CtClass initManagerCtClass=classPool.get("com.appinit.lib.AppInitialManager")

    mProject.logger.error(">>  androidAppCtClass  ${androidAppCtClass.name}")
    mProject.logger.error(">>  initCtClass  ${initCtClass.name}")
    //mProject.logger.error(">>  initManagerCtClass  ${initManagerCtClass.name}")

    classPool.importPackage("android.os")
    classPool.importPackage("android.util")

    if (!file.name.endsWith("class")) {
      return false
    }

    CtClass ctClass
    try {
      ctClass = classPool.makeClass(new FileInputStream(file))
    } catch (Throwable throwable) {
      mProject.logger.error(">>  Parsing class file ${file.getAbsolutePath()} fail.", throwable)
      return false
    }

    mProject.logger.error(">>  checkAndTransformClass  ${ctClass.name}")
    // AppInitial ....
    boolean handled = false
    if (androidAppCtClass != null && ctClass.subclassOf(androidAppCtClass)) {
      //判断是否有AppInitial 注解
      if(ctClass.getAnnotation(AppInitial.class)!=null){
        mProject.logger.error(">>  FindApplication ${ctClass.getAnnotation(AppInitial.class)} ")
        appCtClass = ctClass
        appDest = dest
        handled = true
      }
    }
    // provider
    if(androidProviderCtClass!=null && ctClass.subclassOf(androidProviderCtClass)){
      if(ctClass.getAnnotation(AppInitial.class)!=null){
        mProject.logger.error(">>  findApplication ${ctClass.getAnnotation(AppInitial.class)}")
        appProviderClass=ctClass;
        appProviderDest=dest
        handled=true
      }
    }

    // AppInitialManager
//    if(initManagerCtClass!=null && ctClass.subclassOf(initManagerCtClass)){
//      mProject.logger.error(">>  findApplication ${ctClass.getName().toString()}")
//      appInitManagerClass=ctClass;
//      appInitManagerDest=dest;
//      handled=true
//    }

    //Initial..
    if (initCtClass != null && ctClass.getInterfaces().contains(initCtClass)) {
      def initItem=new AppInitItem()
      initItem.setClassPath(ctClass.name)

      Object annotation =  ctClass.getAnnotation(Initial.class)
      mProject.logger.error(">>  FindApplication ${ctClass.getAnnotation(Initial.class)} ")
      if (annotation != null) {
        Initial appInit = (Initial)annotation
        initItem.setBackground(appInit.background())
        initItem.setDelay(appInit.delay())
        initItem.setOnlyInDebug(appInit.onlyInDebug())
        initItem.setInChildProcess(appInit.inChildProcess())
        initItem.setPriority(appInit.priority())
      }
      initPath.add(initItem)
      mProject.logger.error(">>  FindAppInit  ${ctClass.name} ")
    }

    return handled
  }


}
